// Author: Long Nguyen
// CS 5600 - Spring 2010
// Lab 4: Lighting
// Date: 02/22/2010


#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#elif defined(_WIN32)
#include <windows.h>
#include <GL/glut.h>
#else
#include <cstdlib>
#include <GL/glut.h>
#endif

#include <cstdio>
#include <cmath>

using namespace std;

/* Global Variables */

int _main_window;
int _pos_x = 50;
int _pos_y = 50;
int _width = 1000;
int _height = 900;
GLUquadricObj *quadric;					// Quadratic For Drawing Spheres
const char* _window_title = "Lab 4: Lighting";

// Sphere Rotation
float _rot_angle_small = 0.0;
float _rot_angle_large = 0.0;
float _rot_sphere_step = 0.1;
// Scene Rotation
float _rot_angle_scene = 0.0;
float _rot_scene_step = 0.1;
// Scene Translation
enum TRANSLATION_MODE {TRANS_RIGHT, TRANS_UP, TRANS_LEFT, TRANS_DOWN, TRANS_IN, TRANS_OUT};
//TRANSLATION_MODE _trans_mode = TRANS_UP;
TRANSLATION_MODE _trans_mode = TRANS_RIGHT;

float _trans_scene_vector[3] = {-25, 0, -25};
//float _trans_scene_vector[2] = {-25, -25};
float _trans_scene_step = 0.1;
// Scene Animation Toggle
bool _bToggleAnimation = true;

bool _isLightFixed = true;			// toggle between fixed and moving light

GLfloat	LightPos[] = {20, 50, -10, 1.0f};				// Light Position, 1: positional

GLfloat brass_am[] = {0.329412, 0.223529, 0.027451, 1.0};
GLfloat brass_diff[] = {0.780392, 0.568627, 0.113725, 1.0};
GLfloat brass_spec[] = {0.992157, 0.941176, 0.807843, 1.0};
GLfloat brass_shi[] = {27.8974};

GLfloat pewter_am[] = {0.105882, 0.058824, 0.113725, 1.0};
GLfloat pewter_diff[] = {0.427451, 0.470588, 0.541176,  1.0};
GLfloat pewter_spec[] = {0.333333, 0.333333, 0.521569,  1.0};
GLfloat pewter_shi[] = {9.84615};

GLfloat polSilver_am[] = {0.23125, 0.23125, 0.23125, 1.0};
GLfloat polSilver_diff[] = {0.2775, 0.2775, 0.2775, 1.0};
GLfloat polSilver_spec[] = {0.773911, 0.773911, 0.773911, 1.0};
GLfloat polSilver_shi[] = {89.6};

GLfloat redPlastic_am[] = {1.0, 0.0, 0.0, 1.0};
GLfloat redPlastic_diff[] = {0.01, 0.01, 0.01, 1.0};
GLfloat redPlastic_spec[] = {0.50, 0.50, 0.50, 1.0};
GLfloat redPlastic_shi[] = {32.0};

GLfloat blkPlastic_am[] = {0.0, 0.0, 0.0, 1.0};
GLfloat blkPlastic_diff[] = {0.01, 0.01, 0.01, 1.0};
GLfloat blkPlastic_spec[] = {0.50, 0.50, 0.50, 1.0};
GLfloat blkPlastic_shi[] = {10.0};


GLfloat blkRubber_am[] = {0.02, 0.02, 0.02, 1.0};
GLfloat blkRubber_diff[] = {0.01, 0.01, 0.01, 1.0};
GLfloat blkRubber_spec[] = {0.4, 0.4, 0.4, 1.0};
GLfloat blkRubber_shi[] = {32.0};

GLfloat grnPlastic_am[] = {0.0, 1.0, 0.0, 1.0};
GLfloat grnPlastic_diff[] = {0.01, 0.01, 0.01, 1.0};
GLfloat grnPlastic_spec[] = {0.50, 0.50, 0.50, 1.0};
GLfloat grnPlastic_shi[] = {10.0};

GLfloat gold_am[] = {0.24725, 0.1995, 0.0745, 1.0};
GLfloat gold_diff[] = {0.75164, 0.60648, 0.22648, 1.0};
GLfloat gold_spec[] = {0.628281, 0.555802, 0.366065, 1.0};
GLfloat gold_shi[] = {51.2};

GLfloat bronze_am[] = {0.2125, 0.1275, 0.054, 1.0};
GLfloat bronze_diff[] = {0.714, 0.4284, 0.18144, 1.0};
GLfloat bronze_spec[] = {0.393548, 0.271906, 0.166721, 1.0};
GLfloat bronze_shi[] = {25.6};


/* Function Declaration */

// GLUT Callback Functions
void idle();
void keyboard(unsigned char key, int x, int y);
void reshape(int width, int height);
void display();

// Initialization Functions
void initGL();

void renderScene();

// Drawing functions
void drawCube();
void drawCubeAssembly();
void drawGround();
void drawSphereAssembly( const GLfloat *big_am,
						const GLfloat *big_diff,
						const GLfloat *big_spec,
						const GLfloat *big_shi,
						const GLfloat *small_am,
						const GLfloat *small_diff,
						const GLfloat *small_spec,
						const GLfloat *small_shi );

/* Program Main Entry Function */

int main(int argc,	char* argv[])
{
	//
	// create the glut window
	//
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(_width, _height);
	glutInitWindowPosition(_pos_x, _pos_y);
	_main_window = glutCreateWindow(_window_title);


	// GLUT Callback Functions
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);
	glutReshapeFunc(reshape);
	glutIdleFunc(idle);

	// Initialize GL
	initGL();

	// give control over to glut
	glutMainLoop();

	return 0;
}


/* Function Definition */

void initGL()
{	
	// Light Parameters
	GLfloat	LightAmb[] = {0.7f, 0.7f, 0.7f, 1.0f};			// Ambient Light
	GLfloat	LightDif[] = {0.9f, 0.9f, 0.9, 1.0f};			// Diffuse Light
	
	//GLfloat	LightPos[] = {0, 200, 200, 0};				// Infinitely far away light source


	glClearColor(0,0,0,0);			//Clear background to black 	
	glShadeModel(GL_SMOOTH);		// Use smooth shading.
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);			// Really Nice Perspective Calculations

	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);				// Set The Ambient Lighting For Light0
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif);				// Set The Diffuse Lighting For Light0
	glLightfv(GL_LIGHT0, GL_SPECULAR, LightDif);			// Set The Specular Lighting For Light0
	
		
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
	//glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 30);

	//glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2);			// enable lighting linear attenuation
	//glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1);			// enable lighting linear attenuation

	glEnable(GL_LIGHT0);							// Enable Light 0
	glEnable(GL_LIGHTING);							// Enable Lighting	
	glEnable(GL_DEPTH_TEST);						// Enable Depth Test

	quadric = gluNewQuadric();					// Create A New Quadratic
	//gluQuadricNormals(quadric, GL_SMOOTH);	
}


void idle(void)
{
	if (glutGetWindow() != _main_window)
		glutSetWindow(_main_window);

	// Update Scene Rotation Angle
	_rot_angle_scene += _rot_scene_step;
	_rot_angle_scene = fmodf(_rot_angle_scene, 360.f);

	// Update Sphere Assembly Rotation Angle
	//
	// Large Sphere
	_rot_angle_large -= _rot_sphere_step;
	_rot_angle_large = fmodf(_rot_angle_large, 360.f);
	// Small Sphere
	_rot_angle_small += _rot_sphere_step;
	_rot_angle_small = fmodf(_rot_angle_small, 360.f);

	// Update Scene Translation
	if (_trans_scene_vector[0] > 25)		// Right side of rectangle
	{
		_trans_mode = TRANS_IN;
		//_trans_mode = TRANS_UP;
	}
	else if (_trans_scene_vector[0] < -25)	// Left side of rectangle
	{
		_trans_mode = TRANS_OUT;
		//_trans_mode = TRANS_DOWN;
	}
	else if (_trans_scene_vector[2] > 25)	// Top side of rectangle
	{
		_trans_mode = TRANS_RIGHT;
	}
	else if (_trans_scene_vector[2] < -25)	// Bottom side of rectangle
	{
		_trans_mode = TRANS_LEFT;
	}
	//else if (_trans_scene_vector[1] > 25)	// Top side of rectangle
	//{
	//	_trans_mode = TRANS_LEFT;
	//}
	//else if (_trans_scene_vector[1] < -25)	// Bottom side of rectangle
	//{
	//	_trans_mode = TRANS_RIGHT;
	//}

	switch(_trans_mode)
	{
	case TRANS_RIGHT:
		_trans_scene_vector[0] += _trans_scene_step;
		_trans_scene_vector[2] = 25;
		break;
	case TRANS_IN:
		_trans_scene_vector[0] = 25;
		_trans_scene_vector[2] -= _trans_scene_step;		
		break;
	case TRANS_LEFT:
		_trans_scene_vector[0] -= _trans_scene_step;
		_trans_scene_vector[2] = -25;
		break;	
	case TRANS_OUT:
		_trans_scene_vector[0] = -25;
		_trans_scene_vector[2] += _trans_scene_step;
		break;

	//case TRANS_RIGHT:
	//	_trans_scene_vector[0] += _trans_scene_step;
	//	_trans_scene_vector[1] = -25;
	//	break;
	//case TRANS_UP:
	//	_trans_scene_vector[0] = 25;
	//	_trans_scene_vector[1] += _trans_scene_step;
	//	break;
	//case TRANS_LEFT:
	//	_trans_scene_vector[0] -= _trans_scene_step;
	//	_trans_scene_vector[1] = 25;
	//	break;
	//case TRANS_DOWN:
	//	_trans_scene_vector[0] = -25;
	//	_trans_scene_vector[1] -= _trans_scene_step;
	//	break;
	}

	// Keep redrawing the scene
	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	switch(key)
	{
		// Light stays stationary or moving with eye.
	case 'c':
	case 'C':
		_isLightFixed = !_isLightFixed;
		break;

		// Reduce cube assembly rotation speed
	case '-':
	case '_':
		_rot_sphere_step /= 1.1;
		break;

		// Increase cube assembly rotation speed
	case '=':
	case '+':
		_rot_sphere_step *= 1.1;
		break;

		// Reduce scene rotation speed
	case 's':
	case 'S':
		_rot_scene_step /= 1.1;
		break;

		// Increase scene rotation speed
	case 'f':
	case 'F':
		_rot_scene_step *= 1.1;
		break;

		// Toggle Pausing of Scene Animation
	case 'p':
	case 'P':
		if (_bToggleAnimation)
		{
			// Animating: Pause the animation
			glutIdleFunc(NULL);
		}
		else
		{
			// Not Animating: Start the animation
			glutIdleFunc(idle);
		}
		_bToggleAnimation = !_bToggleAnimation;
		break;

		// quit
	case 27: 
	case 'q':
	case 'Q':
		exit(EXIT_SUCCESS);
		break;
	}
	glutPostRedisplay();
}

void reshape(int width, int height)
{
	_width = width;
	_height = height;
	glViewport(0, 0, _width, _height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(90, static_cast<double>(_width) / static_cast<double>(_height), 0.01, 1000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glutPostRedisplay();
}

void display( )
{	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glClearColor(0,0,0,0);		// Clear background to black

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();		

	//glPushMatrix();

	// Set the camera location
	//gluLookAt(-10, 100, 60,			// eye
	//			0, 5, 0,			// center
	//			0, 1, 0);			// up	

	glRotatef(45, 1, 0, 0);
	glTranslatef(10, -100, -60);

	glPushMatrix();

	renderScene();	
	
	glPopMatrix();		
	
	//glPopMatrix();
	if (_isLightFixed) {
		glLoadIdentity();		
	}
	glLightfv(GL_LIGHT0, GL_POSITION, LightPos);			// Set The Position For Light0		

	glutSwapBuffers();
}

// Draws objects in scene.
//
void renderScene() {
		
	glPushMatrix();
	
	// Ground plane
	drawGround();

	/* putting Rotate() here will rotate whole scene */
	//
	// Rotate whole scene
	glRotatef(_rot_angle_scene, 0, 1, 0);

	// Translate the whole scene
	glTranslatef(_trans_scene_vector[0], _trans_scene_vector[1], _trans_scene_vector[2]);

	//glRotatef(_rot_angle_scene, 0, 1, 0);
	
	
	// Corner 1
	glPushMatrix();
		glTranslatef(10, 20, 10);
		drawSphereAssembly( brass_am, brass_diff, brass_spec, brass_shi,
							pewter_am, pewter_diff, pewter_spec, pewter_shi );							
	glPopMatrix();
			
	// Corner 2
	glPushMatrix();
		glTranslatef(-10, 20, 10);
		drawSphereAssembly( polSilver_am, polSilver_diff, polSilver_spec, polSilver_shi,
							redPlastic_am, redPlastic_diff, redPlastic_spec, redPlastic_shi );
	glPopMatrix();


	// Corner 3
	glPushMatrix();
		glTranslatef(10, 20, -10);
		drawSphereAssembly( blkPlastic_am, blkPlastic_diff, blkPlastic_spec, blkPlastic_shi,
							gold_am, gold_diff, gold_spec, gold_shi );						
	glPopMatrix();


	// Corner 4
	glPushMatrix();
		glTranslatef(-10, 20, -10);
		drawSphereAssembly( grnPlastic_am, grnPlastic_diff, grnPlastic_spec, grnPlastic_shi,
							blkRubber_am, blkRubber_diff, blkRubber_spec, blkRubber_shi );	
	glPopMatrix();

	glPopMatrix();
}

void drawGround() {

	glMaterialfv(GL_FRONT, GL_AMBIENT, bronze_am);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, bronze_diff);
	glMaterialfv(GL_FRONT, GL_SPECULAR, bronze_spec);
	glMaterialfv(GL_FRONT, GL_SHININESS, bronze_shi);

	glBegin(GL_QUADS);
		// vertices in counter-clockwise order
		glVertex3f(500, 0, 500);
		glVertex3f(500, 0, -500);
		glVertex3f(-500, 0, -500);
		glVertex3f(-500, 0, 500);				
	glEnd();

}

void drawSphere() {

	gluSphere(quadric, 10, 32, 16);
}


void drawSphereAssembly(const GLfloat *big_am,
						const GLfloat *big_diff,
						const GLfloat *big_spec,
						const GLfloat *big_shi,
						const GLfloat *small_am,
						const GLfloat *small_diff,
						const GLfloat *small_spec,
						const GLfloat *small_shi) 
{
		
	glMaterialfv(GL_FRONT, GL_AMBIENT, big_am);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, big_diff);
	glMaterialfv(GL_FRONT, GL_SPECULAR, big_spec);
	glMaterialfv(GL_FRONT, GL_SHININESS, big_shi);

	// Large sphere
	glPushMatrix();
		glTranslatef(0, 20, 0);
		glRotatef(_rot_angle_large, 0, 1, 0);
		drawSphere();
	glPopMatrix();

	glMaterialfv(GL_FRONT, GL_AMBIENT, small_am);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, small_diff);
	glMaterialfv(GL_FRONT, GL_SPECULAR, small_spec);
	glMaterialfv(GL_FRONT, GL_SHININESS, small_shi);

	// Small sphere
	glPushMatrix();
		glTranslatef(0, 35, 0);
		glScalef(0.5, 0.5, 0.5);
		glRotatef(_rot_angle_small, 0, 1, 0);
		drawSphere();
	glPopMatrix();

	//gluSphere(
}

void drawCube()
{
	glBegin(GL_QUAD_STRIP);
		// Quad 1
		glColor3ub(188, 143, 143);	glVertex3f(-5, 10, -5);
		glColor3ub(255, 20, 147);	glVertex3f(-5, 0, -5);
		glColor3ub(176, 224, 230);	glVertex3f(5, 10, -5);
		glColor3ub(139, 125, 107);	glVertex3f(5, 0, -5);
		// Quad 2
		glColor3ub(230, 230, 250);	glVertex3f(5, 10, 5);
		glColor3ub(106, 90, 205);	glVertex3f(5, 0, 5);
		// Quad 3
		glColor3ub(46, 139, 87);	glVertex3f(-5, 10, 5);
		glColor3ub(255, 215, 0);	glVertex3f(-5, 0, 5);
		// Quad 4
		glColor3ub(205, 92, 92);	glVertex3f(-5, 10, -5);
		glColor3ub(255, 20, 147);	glVertex3f(-5, 0, -5);
	glEnd();

	glBegin(GL_QUADS);
		// Top
		glColor3ub(188, 143, 143);	glVertex3f(-5, 10, -5);
		glColor3ub(176, 224, 230);	glVertex3f(5, 10, -5);
		glColor3ub(230, 230, 250);	glVertex3f(5, 10, 5);
		glColor3ub(46, 139, 87);	glVertex3f(-5, 10, 5);
		// Bottom
		glColor3ub(255, 20, 147);	glVertex3f(-5, 0, -5);
		glColor3ub(139, 125, 107);	glVertex3f(5, 0, -5);
		glColor3ub(106, 90, 205);	glVertex3f(5, 0, 5);
		glColor3ub(255, 215, 0);	glVertex3f(-5, 0, 5);
	glEnd();
}


void drawCubeAssembly()
{
	// Large Cube
	glPushMatrix();
	glRotatef(_rot_angle_large, 0, 1, 0);
	drawCube();
	glPopMatrix();

	// Small Cube
	glPushMatrix();
	glTranslatef(0, 10, 0);
	glScalef(0.5, 0.5, 0.5);
	glRotatef(_rot_angle_small, 0, 1, 0);
	drawCube();
	glPopMatrix();
}
